package cn.tannn.surenessdemo.sureness.config;

import cn.tannn.surenessdemo.sureness.annotation.MyAnnotationPathTreeProvider;
import cn.tannn.surenessdemo.jwt.JwtConfig;
import cn.tannn.surenessdemo.sureness.processor.CustomRedisProcessor;
import cn.tannn.surenessdemo.sureness.provider.DatabaseAccountProvider;
import cn.tannn.surenessdemo.sureness.subject.CustomHeaderTokenSubjectCreator;
import cn.tannn.surenessdemo.sureness.subject.CustomPasswdSubjectCreator;
import com.usthe.sureness.matcher.DefaultPathRoleMatcher;
import com.usthe.sureness.matcher.PathTreeProvider;
import com.usthe.sureness.matcher.TreePathRoleMatcher;
import com.usthe.sureness.mgt.SurenessSecurityManager;
import com.usthe.sureness.processor.DefaultProcessorManager;
import com.usthe.sureness.processor.Processor;
import com.usthe.sureness.processor.ProcessorManager;
import com.usthe.sureness.processor.support.NoneProcessor;
import com.usthe.sureness.processor.support.PasswordProcessor;
import com.usthe.sureness.provider.annotation.AnnotationPathTreeProvider;
import com.usthe.sureness.subject.SubjectFactory;
import com.usthe.sureness.subject.SurenessSubjectFactory;
import com.usthe.sureness.subject.creater.BasicSubjectServletCreator;
import com.usthe.sureness.subject.creater.JwtSubjectServletCreator;
import com.usthe.sureness.subject.creater.NoneSubjectServletCreator;
import com.usthe.sureness.util.JsonWebTokenUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * todo 权限认证第二步 (加载 sureness 需要用基础配置)
 * @author tomsun28
 * @date 22:40 2020-03-02
 */
@Configuration
public class SurenessConfiguration {


    /**
     * 加载不同的验证处理器
     *  - header token
     *  - header username/passowrd
     *  - Authorization token
     *  - Authorization username/passowrd
     * @param accountProvider 加载SurenessAccount.loadAccount为验证器加载用户数据
     * @return ProcessorManager
     */
    @Bean
    ProcessorManager processorManager(DatabaseAccountProvider accountProvider) {
        // 处理器初始化集合组
        List<Processor> processorList = new LinkedList<>();
        // todo  use default none processor 还未做权限的动态刷新
        NoneProcessor noneProcessor = new NoneProcessor();
        processorList.add(noneProcessor);
        /*
         * header token 认证处理器 (基于redis实现动态的权限和用户状态)
         * 我这里加载的Subject 是默认的 JwtSubject ，因为我直接继承的JwtProcessor
         */
        CustomRedisProcessor redisProcessor = new CustomRedisProcessor();
        // 加载数据源 - 加载用户用于验证的
        redisProcessor.setAccountProvider(accountProvider);
        processorList.add(redisProcessor);

        // todo use default basic auth processor 还未做权限的动态刷新
        PasswordProcessor passwordProcessor = new PasswordProcessor();
        passwordProcessor.setAccountProvider(accountProvider);
        processorList.add(passwordProcessor);
        return new DefaultProcessorManager(processorList);
    }

    /**
     * 初始化加载资源树
     * @param databasePathTreeProvider 资源树
     */
    @Bean
    TreePathRoleMatcher pathRoleMatcher(PathTreeProvider databasePathTreeProvider) {
        // 我没有加载sureness.yml，不需要了。
        /*
          通过注解加载资源树 @RequiresRoles @WithoutAuth
          @RequiresRoles(roles = {"role1", "role2"}, mapping = "/resource", method = "post")
          其表示资源 /resource===post 的需要角色 role1或者role2才能访问
          @WithoutAuth(mapping = "/resource/*", method = "put")
          其表示资源 /resource/*===put 的可以被任何请求访问
         */
        AnnotationPathTreeProvider annotationPathTreeProvider = new AnnotationPathTreeProvider();
        // 设置AnnotationLoader要扫描的包路径，其会扫描包路径下所有类方法上的@RequiresRoles, @WithoutAuth 注解获取数据
        // todo 实在扫描根包,考虑放进配置文件，或者直接在setScanPackages中写 正则匹配所有controller
        annotationPathTreeProvider.setScanPackages(Collections.singletonList("cn.tannn.surenessdemo.controller"));
        /*
          同 AnnotationPathTreeProvider,此处为我方习惯用的注解
         */
        MyAnnotationPathTreeProvider myAnnotationPathTreeProvider = new MyAnnotationPathTreeProvider();
        myAnnotationPathTreeProvider.setScanPackages(Collections.singletonList("cn.tannn.surenessdemo.controller"));

        // 开始初始化资源权限匹配器，加载
        DefaultPathRoleMatcher pathRoleMatcher = new DefaultPathRoleMatcher();
        pathRoleMatcher.setPathTreeProviderList(Arrays.asList(
                // 默认注解加载资源
                annotationPathTreeProvider,
                // 自定义注解加载资源
                myAnnotationPathTreeProvider,

                /*
                 * DatabasePathTreeProvider 从数据库来
                 * - 加载 需拦截的资源（即拦截资源) , eg: /api/v2/host===post===[role2,role3,role4]）
                 * - 加载 不被拦截的资源（即放行资源)，例如/api/v2/host===post
                 */
                databasePathTreeProvider));
        // 使用资源权限配置数据来建立对应的匹配树
        pathRoleMatcher.buildTree();
        return pathRoleMatcher;
    }

    /**
     * 加载 各种 subject ，即 密钥获取方式,跟Processor搭配，是Processor的获取 subject 的前置条件
     *  - header token
     *  - header username/passowrd
     *  - Authorization token
     *  - Authorization username/passowrd
     * @return SubjectFactory
     */
    @Bean
    SubjectFactory subjectFactory() {
        // SubjectFactory init
        SubjectFactory subjectFactory = new SurenessSubjectFactory();
        // 注册我们需要的SubjectCreator
        subjectFactory.registerSubjectCreator(Arrays.asList(
                // 注意! 强制必须首先添加一个 noSubjectCreator，用来组装 /resource===post (资源===请求方式)的，猜测后去的验证可能用它的数据
                new NoneSubjectServletCreator(),
                // 注册用来创建PasswordSubject的creator
                new BasicSubjectServletCreator(),
                //  注册用来创建JwtSubject的creator:  Bearer token: token
                new JwtSubjectServletCreator(),
                //  注册用来创建PasswdSubject的creator: header : password,username
                new CustomPasswdSubjectCreator(),
                //  注册用来创建HeaderTokenSubject的creator: header:token
                new CustomHeaderTokenSubjectCreator()
                ));
        return subjectFactory;
    }

    /**
     * 安全管理器。
     * @param processorManager 验证器
     * @param pathRoleMatcher 资源树
     * @param subjectFactory subject（获取调用是传入的待验证数据，即密钥token之类的
     * @param jwtConfig jwt全局配置
     * @return SurenessSecurityManager
     */
    @Bean
    SurenessSecurityManager securityManager(ProcessorManager processorManager,
                                            TreePathRoleMatcher pathRoleMatcher,
                                            SubjectFactory subjectFactory,
                                            JwtConfig jwtConfig) {

        // 注意： 设置全局的 jwt 密钥， 办法token需要用（有默认但不建议使用
        JsonWebTokenUtil.setDefaultSecretKey(jwtConfig.getTokenSecret());
        // 把上面初始化好的配置bean整合到一起初始化surenessSecurityManager
        // surenessSecurityManager init
        SurenessSecurityManager securityManager = SurenessSecurityManager.getInstance();
        securityManager.setPathRoleMatcher(pathRoleMatcher);
        securityManager.setSubjectFactory(subjectFactory);
        securityManager.setProcessorManager(processorManager);
        return securityManager;
    }

}
